package lg.minecraft.plugin.library.command;

import lg.minecraft.plugin.library.LGPlugin;
import lg.minecraft.plugin.library.command.arg.ArgType;
import lg.minecraft.plugin.library.config.message.LGPluginMessage;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author langle__
 * @version 1.0
 */
@Getter
public class LGCommand extends BaseCommand implements TabExecutor {

    private final Map<String, SubCommand> subCommandMap = new LinkedHashMap<>();
    private final Map<String, String> aliasMapping = new HashMap<>();

    private final LGPlugin plugin;
    private final String alias;

    public LGCommand(LGPlugin plugin, String name, String... aliases) {
        super(name, aliases);
        this.plugin = plugin;
        this.alias = aliases.length > 0 ? aliases[0] : this.name;
        try {
            register(this);
        } catch (ReflectiveOperationException e) {
            plugin.severe("注册命令 {0} 时发生异常: {1}", name, e.getMessage());
        }
    }

    @Override
    public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
        if (!isCommand(sender, this)) return false;
        if (subCommandMap.isEmpty() || args.length == 0) return handleRootCommand(sender, args);
        return handleSubCommand(sender, args);
    }

    private boolean handleRootCommand(CommandSender sender, String[] args) {
        if (!validateArgs(sender, getArgList(), args)) return false;
        this.onCommand(new CommandContext(sender, args));
        return true;
    }

    private boolean handleSubCommand(CommandSender sender, String[] args) {
        if (args.length == 0) return false;

        SubCommand subCommand = getSubCommand(args[0]);
        if (subCommand == null) {
            sender.sendMessage(plugin.getLGMessage().getUnknownCommand());
            return false;
        }

        if (!isCommand(sender, subCommand)) return false;

        String[] tempArgs = Arrays.copyOfRange(args, 1, args.length);
        if (!validateArgs(sender, subCommand.getArgList(), tempArgs)) return false;

        subCommand.onCommand(new CommandContext(sender, tempArgs));
        return true;
    }

    @Override
    public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
        if (canTabComplete(sender, this)) {
            return Collections.emptyList();
        }

        if (subCommandMap.isEmpty()) {
            return handleRootTabComplete(sender, args);
        }

        return handleSubTabComplete(sender, args);
    }

    private List<String> handleRootTabComplete(CommandSender sender, String[] args) {
        List<String> list = this.onTabComplete(new CommandContext(sender, args));
        if (list == null) {
            list = handleTabCompletion(args, this.getArgList());
        }
        return list == null ? Collections.emptyList() : list;
    }

    private List<String> handleSubTabComplete(CommandSender sender, String[] args) {
        if (args.length == 1) {
            List<String> collect = subCommandMap.values().stream().map(SubCommand::getName).distinct().collect(Collectors.toList());
            return tabValue(args[0], new ArrayList<>(collect));
        }

        SubCommand subCommand = subCommandMap.get(args[0].toLowerCase());
        if (subCommand == null || (sender instanceof Player && !subCommand.isPermission(sender))) {
            return Collections.emptyList();
        }

        String[] tempArgs = Arrays.copyOfRange(args, 1, args.length);
        List<String> list = subCommand.onTabComplete(new CommandContext(sender, tempArgs));
        if (list == null) list = handleTabCompletion(tempArgs, subCommand.getArgList());
        if (list == null) return Collections.emptyList();

        return tabValue(tempArgs[tempArgs.length - 1], list);
    }

    @Nullable
    public SubCommand getSubCommand(@NotNull String name) {
        String commandName = name.toLowerCase();

        SubCommand command = subCommandMap.get(commandName);
        if (command != null) return command;

        String primaryName = aliasMapping.get(commandName);
        return primaryName != null ? subCommandMap.get(primaryName) : null;
    }

    public void registerSubCommand(@NotNull SubCommand subCommand) {
        if (subCommand.getParentCommand() != null) {
            throw new IllegalArgumentException("SubCommand is already registered!");
        }

        if (!subCommand.canRegister()) return;

        subCommand.setParentCommand(this);

        addSubCommand(subCommand);
    }

    public void unregisterSubCommand(@NotNull SubCommand command) {
        removeSubCommand(command);
    }

    public void unregisterSubCommand(@NotNull String commandName) {
        SubCommand subCommand = subCommandMap.get(commandName.toLowerCase());
        removeSubCommand(subCommand);
    }

    private void addSubCommand(@NotNull SubCommand command) {
        String commandName = command.getName().toLowerCase();
        subCommandMap.put(commandName, command);

        for (String alias : command.getAliases()) {
            aliasMapping.put(alias.toLowerCase(), commandName);
        }
    }

    private void removeSubCommand(@NotNull SubCommand command) {
        String commandName = command.getName().toLowerCase();
        subCommandMap.remove(commandName);

        for (String alias : command.getAliases()) {
            aliasMapping.remove(alias.toLowerCase(), commandName);
        }
    }

    public Set<String> getSubCommandNames() {
        return subCommandMap.keySet();
    }

    public Collection<SubCommand> getSubCommands() {
        return subCommandMap.values();
    }

    /**
     * 检查发送命令的实体是否有权限执行该命令
     * 此方法主要用于判断命令发送者是否符合命令执行的条件，包括命令类型和权限检查
     *
     * @param sender 命令发送者，可以是玩家或控制台
     * @param command 被检查的命令对象
     * @return 返回false表示命令发送者不符合执行条件，返回true表示符合执行条件
     */
    private boolean isCommand(CommandSender sender, BaseCommand command) {
        CommandType type = command.getCommandType();
        if (type.test(sender)) {
            if (CommandType.PLAYER.test(sender)) {
                if (command.isPermission(sender)) return true;
                sender.sendMessage(plugin.getLGMessage().getNoPermission(command.getPermission()));
                return false;
            }
            return true;
        }
        if (CommandType.SERVER.test(sender) && type == CommandType.PLAYER) {
            sender.sendMessage(plugin.getLGMessage().getNoPlayer());
            return false;
        }
        if (CommandType.PLAYER.test(sender) && type != CommandType.PLAYER) {
            sender.sendMessage(plugin.getLGMessage().getNoPlayer());
            return false;
        }
        return false;
    }

    /**
     * 检查命令发送者是否可以使用tab自动完成命令
     *
     * @param sender 命令发送者，可以是玩家或控制台
     * @param command 被检查的命令对象
     * @return 如果发送者可以使用tab自动完成，则返回true；否则返回false
     */
    private boolean canTabComplete(CommandSender sender, BaseCommand command) {
        CommandType type = command.getCommandType();
        if (type == CommandType.CONSOLE && sender instanceof Player) return true;
        if (type == CommandType.PLAYER && sender instanceof ConsoleCommandSender) return true;
        if (sender instanceof Player && !command.isPermission(sender)) return true;
        return false;
    }

    private boolean validateArgs(CommandSender sender, ArgType[] argList, String[] args) {
        if (argList != null && argList.length > 0) {
            LGPluginMessage message = plugin.getLGMessage();
            for (int i = 0; i < args.length; i++) {
                ArgType argType = argList[i];
                String arg = args[i];
                if (!argType.validate(arg)) {
                    sender.sendMessage(message.getInvalidArg(String.valueOf(i + 1), arg, argType.errorMessage));
                    return false;
                }
            }
            if (args.length < argList.length) {
                sender.sendMessage(message.getLackArg());
                return false;
            }
        }
        return true;
    }

    private List<String> handleTabCompletion(String[] args, ArgType[] argList) {
        int length = args.length;
        if (length == 0) {
            return null;
        }
        int index = length - 1;
        if (argList == null || index >= argList.length) {
            return null;
        }
        ArgType argType = argList[index];
        if (argType == ArgType.PLAYER) {
            return tabValuePlayerName(args[index]);
        } else if (argType == ArgType.NUMBER) {
            return tabValue(args[index], Arrays.asList("10", "100", "1000"));
        } else if (argType == ArgType.BOOLEAN) {
            return tabValue(args[index], Arrays.asList("true", "false"));
        }
        return null;
    }

    @Override
    public void onCommand(CommandContext context) {
        CommandSender sender = context.getSender();
        String[] array = plugin.getLGMessage().getInfoArray(alias);
        for (String s : array) {
            sender.sendMessage(s);
        }
    }

    private static void register(LGCommand lgCommand) throws ReflectiveOperationException {
        Constructor<PluginCommand> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
        constructor.setAccessible(true);
        PluginCommand pluginCommand = constructor.newInstance(lgCommand.getName(), lgCommand.getPlugin());
        pluginCommand.setAliases(Arrays.asList(lgCommand.getAliases()));
        pluginCommand.setExecutor(lgCommand);

        CommandMap commandMap = getCommandMap();
        commandMap.register(lgCommand.getName(), pluginCommand);
    }

    private static CommandMap getCommandMap() throws ReflectiveOperationException {
        Server server = Bukkit.getServer();
        Method getCommandMap = server.getClass().getDeclaredMethod("getCommandMap");
        getCommandMap.setAccessible(true);
        return (CommandMap) getCommandMap.invoke(server);
    }

}
